//==--- tools/clang-check/ClangInterpreter.cpp - Clang Interpreter tool --------------===//
//===----------------------------------------------------------------------===//

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/EvaluatedExprVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"

using namespace clang;
using namespace std;

#include "Environment.hh"
#include "DF4C.hh"
#include <iostream>
#include <fstream>

class InterpreterVisitor : 
	public EvaluatedExprVisitor<InterpreterVisitor> {
public:
	explicit InterpreterVisitor(const ASTContext &context, Environment * env)
	: EvaluatedExprVisitor(context), mEnv(env) {}
	virtual ~InterpreterVisitor() {}

	virtual void Init(TranslationUnitDecl * unit) {
		/*
		 * Before visiting global declarations, initialize the environment
		 * to ensure that `mStack` has a stack frame (global).
		 */
		mEnv->init(unit);
		// Init from translation unit: functions and global variables
		for (auto sd : unit->decls()) {
			if (sd == nullptr)
				continue;
			FunctionDecl * fdecl = dyn_cast<FunctionDecl>(sd);
			VarDecl * vdecl = dyn_cast<VarDecl>(sd);
			if (fdecl != nullptr)
				VisitFunctionDecl(fdecl);
			else if (vdecl != nullptr)
				VisitVarDecl(vdecl);
		}
		FunctionDecl * entry = mEnv->getEntry();
		// Before visiting the entry function body, push a new stack frame (for `main`)
		mEnv->init(nullptr);
		try {
			VisitStmt(entry->getBody());
		} catch (ControlFlowException & cfe) {
			if (!cfe.isReturn())
				throw logic_error("Unexpected CFE");
		}
	}
	virtual void VisitBinaryOperator(BinaryOperator * bop) {
		VisitStmt(bop);
		mEnv->binop(bop);
	}
	virtual void VisitUnaryOperator(UnaryOperator * uop) {
		VisitStmt(uop);
		mEnv->unop(uop);
	}
	virtual void VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr * uettexpr) {
		VisitStmt(uettexpr);
		mEnv->uett(uettexpr);
	}
	virtual void VisitDeclRefExpr(DeclRefExpr * expr) {
		VisitStmt(expr);
		mEnv->declref(expr);
	}
	virtual void VisitCastExpr(CastExpr * expr) {
		VisitStmt(expr);
		mEnv->cast(expr);
	}
	virtual void VisitCallExpr(CallExpr * call) {
		VisitStmt(call);
		if (mEnv->call(call) != 0l)
		{
			FunctionDecl * callee = call->getDirectCallee();
			if (callee->isDefined()) {
				callee = callee->getDefinition();
			} else {
				throw logic_error("Called function not defined");
			}
			try {
				// Before visiting the called function body, push a new stack frame
				VisitStmt(callee->getBody());
			} catch (ControlFlowException & cfe) {
				if (!cfe.isReturn())
					throw logic_error("Unexpected CFE");
			}
			mEnv->freturn(call);
		}
	}
	virtual void VisitArraySubscriptExpr(ArraySubscriptExpr * asexpr) {
		VisitStmt(asexpr);
		mEnv->arraysub(asexpr);
	}
	virtual void VisitReturnStmt(ReturnStmt * retstmt) {
		VisitStmt(retstmt);
		Expr * retexpr = retstmt->getRetValue();
		if (retexpr != nullptr)
			mEnv->retstmt(retstmt);
		throw ControlFlowException(ControlFlowException::RETURN);
	}
	virtual void VisitDeclStmt(DeclStmt * declstmt) {
		//mEnv->decl(declstmt);
		VarDecl * vd;
		for (auto sd : declstmt->decls()) {
			if ((vd = dyn_cast<VarDecl>(sd)) != nullptr) {
				VisitVarDecl(vd);
			}
		}
	}
	virtual void VisitVarDecl(VarDecl * vardecl) {
		if (vardecl != nullptr) {
			if (vardecl->hasInit())
				Visit(vardecl->getInit());
			mEnv->vardecl(vardecl);
		}
	}
	virtual void VisitFunctionDecl(FunctionDecl * fdecl) {
		mEnv->fdecl(fdecl);
	}
	virtual void VisitIntegerLiteral(IntegerLiteral * intlit) {
		mEnv->intlit(intlit);
	}
	virtual void VisitCharLiteral(CharacterLiteral * charlit) {
		mEnv->charlit(charlit);
	}
	virtual void VisitParenExpr(ParenExpr * parenexpr) {
		VisitStmt(parenexpr);
		mEnv->paren(parenexpr);
	}
	virtual void VisitIfStmt(IfStmt * ifstmt) {
		Visit(ifstmt->getCond());
		if (mEnv->cond(ifstmt->getCond()) != 0)
			Visit(ifstmt->getThen());
		else {
			Stmt * elsestmt = ifstmt->getElse();
			if (elsestmt != nullptr)
				Visit(elsestmt);
		}
	}
	virtual void VisitWhileStmt(WhileStmt * whilestmt) {
		Visit(whilestmt->getCond());
		while (mEnv->cond(whilestmt->getCond()) != 0) {
			Visit(whilestmt->getBody());
			Visit(whilestmt->getCond());
		}
	}
	virtual void VisitForStmt(ForStmt * forstmt) {
		Stmt * initstmt = forstmt->getInit();
		if (initstmt != nullptr)
			Visit(initstmt);
		Expr * condexpr = forstmt->getCond();
		if (condexpr != nullptr)
			Visit(condexpr);
		while (condexpr == nullptr
			|| mEnv->cond(condexpr) != 0) {
			Visit(forstmt->getBody());
			Stmt * incstmt = forstmt->getInc();
			if (incstmt != nullptr)
				Visit(incstmt);
			if (condexpr != nullptr)
				Visit(condexpr);
		}
	}
	
private:
	Environment * mEnv;
};

class InterpreterConsumer : public ASTConsumer {
public:
	explicit InterpreterConsumer(const ASTContext& context) : mEnv(context),
			mVisitor(context, &mEnv) {
	}
	virtual ~InterpreterConsumer() {}

	virtual void HandleTranslationUnit(clang::ASTContext &Context) {
		TranslationUnitDecl * decl = Context.getTranslationUnitDecl();
		mVisitor.Init(decl);

	}
private:
	Environment mEnv;
	InterpreterVisitor mVisitor;
};

class InterpreterClassAction : public ASTFrontendAction {
public:
	virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
	 clang::CompilerInstance &Compiler, llvm::StringRef InFile) {
	 return std::unique_ptr<clang::ASTConsumer>(
			new InterpreterConsumer(Compiler.getASTContext()));
	}
};

int main (int argc, char ** argv) {
	const char *srcfilename = nullptr;
	string line, srcfilecontent;
	bool flag_file = false;

	while (--argc) {
		++argv;
		if (flag_file) {
			srcfilename = *argv;
			flag_file = false;
		} else if (strcmp(*argv, "-f") == 0) {
			flag_file = true;
		} else {
			srcfilecontent = *argv;
		}
	}
	if (srcfilename == nullptr && srcfilecontent.empty()) {
		cerr << Fatal_error << "no input file" << endl;
		return 1;
	}
	if (srcfilename != nullptr)
	{	// Read the source file
		ifstream srcfile(srcfilename);
		srcfilecontent.clear();
		if (!srcfile.is_open()) {
			cerr << Fatal_error << "cannot open input file: " << srcfilename << endl;
			return 1;
		}
		while (getline(srcfile, line)) {
			srcfilecontent += line + "\n";
		}
	}
	try {
		// Use clang tooling to parse and interpret the source code
		clang::tooling::runToolOnCode(
			std::unique_ptr<clang::FrontendAction>(new InterpreterClassAction),
			srcfilecontent, srcfilename == nullptr ? "liangxiang.c" : srcfilename
		);
		cout << endl;
	}
	catch (const std::runtime_error & e) {
		cerr << Normal_error << e.what() << endl;
		return 2;
	}
	catch (const std::logic_error & e) {
		cerr << Fatal_error << e.what() << endl;
		return 3;
	}
	catch (...) {
		cerr << Fatal_error << "unknown exception occurred" << endl;
		return 3;
	}
	return 0;
}
